/*
 * Copyright (c) 2012-2021 Belledonne Communications SARL.
 *
 * This file is part of belle-sip.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "belle_sip_internal.h"
#include "channel.h"

BELLE_SIP_DECLARE_CUSTOM_VPTR_BEGIN(belle_sip_udp_channel_t,belle_sip_channel_t)
BELLE_SIP_DECLARE_CUSTOM_VPTR_END

struct belle_sip_udp_channel{
	belle_sip_channel_t base;
	belle_sip_socket_t shared_socket; /*the socket that belongs to the listening point. It is stored here because the channel parent class
						may erase its value in the belle_sip_source_t base class*/
};

typedef struct belle_sip_udp_channel belle_sip_udp_channel_t;

static void udp_channel_uninit(belle_sip_udp_channel_t *obj){
	belle_sip_socket_t sock=belle_sip_source_get_socket((belle_sip_source_t*)obj);
	if (obj->shared_socket == SOCKET_NOT_SET && sock != SOCKET_NOT_SET) {
		belle_sip_close_socket(belle_sip_source_get_socket((belle_sip_source_t*)obj));
	} /*else
	no close of the socket, because it is owned by the listerning point and shared between all channels*/
}

static int udp_channel_send(belle_sip_channel_t *obj, const void *buf, size_t buflen){
	belle_sip_udp_channel_t *chan=(belle_sip_udp_channel_t *)obj;
	int err;
	belle_sip_socket_t sock=belle_sip_source_get_socket((belle_sip_source_t*)chan);
	if(sock){
		if (chan->shared_socket != SOCKET_NOT_SET) {
		err=(int)bctbx_sendto(sock,buf,buflen,0,obj->current_peer->ai_addr,(socklen_t)obj->current_peer->ai_addrlen);
		} else {
			/*There is no serveur socket, so we are in connected mode*/
			err=(int)bctbx_send(sock, buf, buflen, 0);
		}
		if (err==-1){
			belle_sip_error("channel [%p]: could not send UDP packet because [%s]",obj,belle_sip_get_socket_error_string());
			return -errno;
		}
	}else {
		belle_sip_error("channel [%p]: no socket are available to send UDP packet because [%s]",obj,belle_sip_get_socket_error_string());
		err = -errno;
	}
	return err;
}

static int udp_channel_recv(belle_sip_channel_t *obj, void *buf, size_t buflen){
	belle_sip_udp_channel_t *chan=(belle_sip_udp_channel_t *)obj;
	int err;
	int errnum;
	struct sockaddr_storage addr;
	socklen_t addrlen=sizeof(addr);
	belle_sip_socket_t sock=belle_sip_source_get_socket((belle_sip_source_t*)chan);

	err=(int)bctbx_recvfrom(sock,buf,buflen,0,(struct sockaddr*)&addr,&addrlen);
	errnum = get_socket_error();
	if (err==-1 && errnum!=BELLESIP_EWOULDBLOCK){
		if (errnum == BCTBX_ENOTCONN) { //Do NOT treat it as an error
			belle_sip_message("Socket is not connected because of IOS10 background policy");
			obj->closed_by_remote = TRUE;
			return 0;
		}
		belle_sip_error("Could not receive UDP packet: %s",belle_sip_get_socket_error_string_from_code(errnum));
		return -errnum;
	}
	return err;
}

int udp_channel_connect(belle_sip_channel_t *obj, const struct addrinfo *ai){
	belle_sip_udp_channel_t *chan=(belle_sip_udp_channel_t *)obj;
	struct sockaddr_storage laddr;
	int err;
	
	memset(&laddr, 0, sizeof(laddr));
	socklen_t lslen=sizeof(laddr);

	if (obj->local_port == BELLE_SIP_LISTENING_POINT_DONT_BIND) {
		//Creating socket for this channel here to be compatible with channel's retry mode
		int port = BELLE_SIP_LISTENING_POINT_RANDOM_PORT;
		int ai_family = obj->lp->ai_family;
		belle_sip_socket_t sock = udp_listening_point_create_udp_socket(belle_sip_uri_get_host(((belle_sip_listening_point_t*)obj->lp)->listening_uri)
									 , &port, &ai_family);
		belle_sip_socket_set_nonblocking(sock);
		if (bctbx_connect(sock,ai->ai_addr,(socklen_t)ai->ai_addrlen)==-1){
			err = -get_socket_error();
			belle_sip_error("bctbx_connect() failed for socket [%i]: cause [%s]",(int)sock,belle_sip_get_socket_error_string_from_code(-err));
			return -1;
		}
		err=bctbx_getsockname(sock,(struct sockaddr*)&laddr,&lslen);
		if (err<0){
			belle_sip_error("Failed to retrieve sockname  for socket [%i]: cause [%s]",(int)sock,belle_sip_get_socket_error_string());
			return -1;
		}
		belle_sip_channel_set_socket(BELLE_SIP_CHANNEL(chan), sock, (belle_sip_source_func_t) belle_sip_channel_process_data);
		belle_sip_source_set_events((belle_sip_source_t*)chan,BELLE_SIP_EVENT_READ|BELLE_SIP_EVENT_ERROR);
		belle_sip_main_loop_add_source(obj->lp->stack->ml,(belle_sip_source_t*)chan);
	} else {
		belle_sip_channel_set_socket(obj, chan->shared_socket, NULL);
		err = belle_sip_get_src_addr_for(ai->ai_addr,(socklen_t)ai->ai_addrlen,(struct sockaddr*)&laddr,&lslen,obj->local_port);
		if (err == -BCTBX_ENETUNREACH || err == -BCTBX_EHOSTUNREACH){
			return -1;
		}
	}
		
	belle_sip_channel_set_ready(obj, (struct sockaddr*)&laddr, lslen);
	return 0;
}

BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(belle_sip_udp_channel_t);

BELLE_SIP_INSTANCIATE_CUSTOM_VPTR_BEGIN(belle_sip_udp_channel_t)
	{
		{
			BELLE_SIP_VPTR_INIT(belle_sip_udp_channel_t,belle_sip_channel_t,FALSE),
			(belle_sip_object_destroy_t)udp_channel_uninit,
			NULL,
			NULL,
			(belle_sip_object_on_first_ref_t) NULL,
			(belle_sip_object_on_last_ref_t) NULL,
			BELLE_SIP_DEFAULT_BUFSIZE_HINT
		},
		"UDP",
		0, /*is_reliable*/
		udp_channel_connect,
		udp_channel_send,
		udp_channel_recv,
		NULL /*no close method*/
	}
BELLE_SIP_INSTANCIATE_CUSTOM_VPTR_END

belle_sip_channel_t * belle_sip_channel_new_udp(belle_sip_stack_t *stack, int sock, const char *bindip, int localport, const char *dest, int port, int no_srv){
	belle_sip_udp_channel_t *obj=belle_sip_object_new(belle_sip_udp_channel_t);
	belle_sip_channel_init((belle_sip_channel_t*)obj,stack,bindip,localport,NULL,dest,port, no_srv);
	obj->shared_socket = sock;
	return (belle_sip_channel_t*)obj;
}

belle_sip_channel_t * belle_sip_channel_new_udp_with_addr(belle_sip_stack_t *stack, int sock, const char *bindip, int localport, const struct addrinfo *peer){
	belle_sip_udp_channel_t *obj=belle_sip_object_new(belle_sip_udp_channel_t);

	belle_sip_channel_init_with_addr((belle_sip_channel_t*)obj, stack, bindip, localport, peer->ai_addr, (socklen_t)peer->ai_addrlen);
	obj->base.local_port=localport;
	obj->shared_socket = sock;
	/*this lookups the local address*/
	udp_channel_connect((belle_sip_channel_t*)obj,peer);
	return (belle_sip_channel_t*)obj;
}
